// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "icosphere.h"

#include <GL/gl.h>
#include <map>

using namespace Eigen;

//--------------------------------------------------------------------------------
// icosahedron data
//--------------------------------------------------------------------------------
#define X .525731112119133606
#define Z .850650808352039932

static GLfloat vdata[12][3] = { { -X, 0.0, Z }, { X, 0.0, Z },	{ -X, 0.0, -Z }, { X, 0.0, -Z },
								{ 0.0, Z, X },	{ 0.0, Z, -X }, { 0.0, -Z, X },	 { 0.0, -Z, -X },
								{ Z, X, 0.0 },	{ -Z, X, 0.0 }, { Z, -X, 0.0 },	 { -Z, -X, 0.0 } };

static GLint tindices[20][3] = { { 0, 4, 1 },  { 0, 9, 4 },	 { 9, 5, 4 },  { 4, 5, 8 },	 { 4, 8, 1 },
								 { 8, 10, 1 }, { 8, 3, 10 }, { 5, 3, 8 },  { 5, 2, 3 },	 { 2, 7, 3 },
								 { 7, 10, 3 }, { 7, 6, 10 }, { 7, 11, 6 }, { 11, 0, 6 }, { 0, 1, 6 },
								 { 6, 1, 10 }, { 9, 0, 11 }, { 9, 11, 2 }, { 9, 2, 5 },	 { 7, 2, 11 } };
//--------------------------------------------------------------------------------

IcoSphere::IcoSphere(unsigned int levels)
{
	// init with an icosahedron
	for (int i = 0; i < 12; i++)
		mVertices.push_back(Map<Vector3f>(vdata[i]));
	mIndices.push_back(new std::vector<int>);
	std::vector<int>& indices = *mIndices.back();
	for (int i = 0; i < 20; i++) {
		for (int k = 0; k < 3; k++)
			indices.push_back(tindices[i][k]);
	}
	mListIds.push_back(0);

	while (mIndices.size() < levels)
		_subdivide();
}

const std::vector<int>&
IcoSphere::indices(int level) const
{
	while (level >= int(mIndices.size()))
		const_cast<IcoSphere*>(this)->_subdivide();
	return *mIndices[level];
}

void
IcoSphere::_subdivide(void)
{
	typedef unsigned long long Key;
	std::map<Key, int> edgeMap;
	const std::vector<int>& indices = *mIndices.back();
	mIndices.push_back(new std::vector<int>);
	std::vector<int>& refinedIndices = *mIndices.back();
	int end = indices.size();
	for (int i = 0; i < end; i += 3) {
		int ids0[3], // indices of outer vertices
			ids1[3]; // indices of edge vertices
		for (int k = 0; k < 3; ++k) {
			int k1 = (k + 1) % 3;
			int e0 = indices[i + k];
			int e1 = indices[i + k1];
			ids0[k] = e0;
			if (e1 > e0)
				std::swap(e0, e1);
			Key edgeKey = Key(e0) | (Key(e1) << 32);
			std::map<Key, int>::iterator it = edgeMap.find(edgeKey);
			if (it == edgeMap.end()) {
				ids1[k] = mVertices.size();
				edgeMap[edgeKey] = ids1[k];
				mVertices.push_back((mVertices[e0] + mVertices[e1]).normalized());
			} else
				ids1[k] = it->second;
		}
		refinedIndices.push_back(ids0[0]);
		refinedIndices.push_back(ids1[0]);
		refinedIndices.push_back(ids1[2]);
		refinedIndices.push_back(ids0[1]);
		refinedIndices.push_back(ids1[1]);
		refinedIndices.push_back(ids1[0]);
		refinedIndices.push_back(ids0[2]);
		refinedIndices.push_back(ids1[2]);
		refinedIndices.push_back(ids1[1]);
		refinedIndices.push_back(ids1[0]);
		refinedIndices.push_back(ids1[1]);
		refinedIndices.push_back(ids1[2]);
	}
	mListIds.push_back(0);
}

void
IcoSphere::draw(int level)
{
	while (level >= int(mIndices.size()))
		const_cast<IcoSphere*>(this)->_subdivide();
	if (mListIds[level] == 0) {
		mListIds[level] = glGenLists(1);
		glNewList(mListIds[level], GL_COMPILE);
		glVertexPointer(3, GL_FLOAT, 0, mVertices[0].data());
		glNormalPointer(GL_FLOAT, 0, mVertices[0].data());
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
		glDrawElements(GL_TRIANGLES, mIndices[level]->size(), GL_UNSIGNED_INT, &(mIndices[level]->at(0)));
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		glEndList();
	}
	glCallList(mListIds[level]);
}
